package com.quectel.sdk.baidu;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.quectel.base.tuple.Tuple2;
import com.quectel.base.tuple.Tuples;
import com.quectel.sdk.baidu.model.BaiduBCEBaseResp;
import com.quectel.sdk.baidu.model.auth.BaiduBCEAuthResp;
import com.quectel.sdk.ex.baidubce.TokenException;
import com.quectel.util.ex.CheckErrorException;
import com.quectel.util.http.HttpRestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 在这里编写说明
 *
 * @author: nolan.zhang
 * @email: nolan.zhang@quectel.com
 * @date: 2023-05-11 11:01:00
 */
public class BaiduBCERequester {

    private static final Logger logger = LoggerFactory.getLogger(BaiduBCERequester.class);

    public final static String HOST = "https://aip.baidubce.com";
    public final static String CONTENT_TYPE_JSON = "application/json";


    private final static String ACCESS_TOKEN = "access_token";
    private final static String GRAND_TYPE = "client_credentials";
    private final static String OAUTH_URI = "/oauth/2.0/token";
    private final static String ERROR_CODE_INVALID_PARAMETER = "100";
    private final static String ERROR_CODE_ACCESS_TOKEN_INVALID = "110";
    private final static String ERROR_CODE_ACCESS_TOKEN_EXPIRED = "111";

    private static final HttpRestUtils HTTP_REST_UTILS = HttpRestUtils.getDefaultInstance();
    private String host;
    private String appKey;
    private String appSecret;


    /**
     * 鉴权token cache
     * cache
     * k1 url
     * k2 appKey
     */
    private static Function<Tuple2<String, String>, String> TOKEN_KEY_FUNC = (t) ->
            "appKey:" + t.getT1() + ":" + t.getT2();

    /**
     * api文档显示token有效期为30天 这里设置1天获取一次
     *
     * @link https://ai.baidu.com/ai-doc/REFERENCE/Lkru0zoz4
     */
    private static final Cache<String, String> TOKEN_CACHE =
            CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS).build();

    private BaiduBCERequester(String host, String appKey, String appSecret) {
        this.host = host;
        this.appKey = appKey;
        this.appSecret = appSecret;
    }


    public static BaiduBCERequester getInstance(String host, String appKey, String appSecret) {

        return new BaiduBCERequester(host, appKey, appSecret);
    }


    public <T extends BaiduBCEBaseResp> T get(String path, Map<String, Object> params, TypeReference<T> typeReference) {
        if (this.host == null) {
            throw new CheckErrorException("checked url " + this.host + " is null");
        }
        String token = getToken();
        if (token == null) {
            throw new TokenException("Obtain baidubce platform token failed");
        }

        params.put(ACCESS_TOKEN, token);

        List<NameValuePair> paramList = new ArrayList<>();

        Set<Map.Entry<String, Object>> entries = params.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            paramList.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
        }

        Pair<Integer, T> get = HTTP_REST_UTILS.get(host + path, paramList, null, typeReference);


        if (get.getRight() != null && (ERROR_CODE_INVALID_PARAMETER.equals(get.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_INVALID.equals(get.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_EXPIRED.equals(get.getRight().getErrorCode()))) {
            TOKEN_CACHE.asMap().remove(TOKEN_KEY_FUNC.apply(Tuples.of(host, appKey)));
            token = getToken();
            params.put(ACCESS_TOKEN, token);
            get = HTTP_REST_UTILS.get(host + path, paramList, null, typeReference);

        }

        return get.getRight();

    }

    public <T extends BaiduBCEBaseResp> T post(String path, Object params, TypeReference<T> typeReference) {
        if (this.host == null) {
            throw new CheckErrorException("checked url " + this.host + " is null");
        }
        String token = getToken();
        if (token == null) {
            throw new TokenException("Obtain baidubce platform token failed");
        }

        //token放在url
        String url = setToken(host + path, token);

        Pair<Integer, T> post = HTTP_REST_UTILS.post(url, params, null, typeReference);

        if (post.getRight() != null && (ERROR_CODE_INVALID_PARAMETER.equals(post.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_INVALID.equals(post.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_EXPIRED.equals(post.getRight().getErrorCode()))) {
            TOKEN_CACHE.asMap().remove(TOKEN_KEY_FUNC.apply(Tuples.of(host, appKey)));
            token = getToken();
            url = setToken(host + path, token);
            post = HTTP_REST_UTILS.post(url, params, null, typeReference);

        }

        return post.getRight();

    }


    public <T extends BaiduBCEBaseResp> T postForm(String path, Map<String, Object> params, TypeReference<T> typeReference) {
        if (this.host == null) {
            throw new CheckErrorException("checked url " + this.host + " is null");
        }
        String token = getToken();
        if (token == null) {
            throw new TokenException("Obtain baidubce platform token failed");
        }

        params.put(ACCESS_TOKEN, token);

        List<NameValuePair> paramList = new ArrayList<>();

        Set<Map.Entry<String, Object>> entries = params.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            paramList.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
        }

        Pair<Integer, T> post = HTTP_REST_UTILS.postForm(host + path, paramList, null, typeReference);


        if (post.getRight() != null && (ERROR_CODE_INVALID_PARAMETER.equals(post.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_INVALID.equals(post.getRight().getErrorCode())
                || ERROR_CODE_ACCESS_TOKEN_EXPIRED.equals(post.getRight().getErrorCode()))) {
            TOKEN_CACHE.asMap().remove(TOKEN_KEY_FUNC.apply(Tuples.of(host, appKey)));
            token = getToken();
            params.put(ACCESS_TOKEN, token);
            post = HTTP_REST_UTILS.postForm(host + path, paramList, null, typeReference);

        }

        return post.getRight();

    }

    public String getToken() {
        return TOKEN_CACHE.asMap().computeIfAbsent(
                TOKEN_KEY_FUNC.apply(Tuples.of(host, appKey)),
                (cacheKey) -> {
                    Map<String, String> headers = Maps.newHashMap();
                    headers.put("Accept", CONTENT_TYPE_JSON);
                    headers.put("Content-Type", CONTENT_TYPE_JSON);

                    StringBuilder url = new StringBuilder(host + OAUTH_URI);
                    url.append("?").append("grant_type=").append(GRAND_TYPE);
                    url.append("&").append("client_id=").append(appKey);
                    url.append("&").append("client_secret=").append(appSecret);

                    BaiduBCEAuthResp response = HTTP_REST_UTILS.post(url.toString(), null, headers, new TypeReference<BaiduBCEAuthResp>() {
                    }).getRight();
                    if (StringUtils.isBlank(response.getAccessToken())) {
                        logger.error("baidubce getToken error ,errorCode :{},errorMsg:{}", response.getErrorCode(), response.getErrorCode());
                        return null;
                    }
                    return response.getAccessToken();
                }
        );
    }


    private String setToken(String url, String token) {
        if (url.contains("?")) {
            return url + "&" + ACCESS_TOKEN + "=" + token;
        } else {
            return url + "?" + ACCESS_TOKEN + "=" + token;
        }
    }


}
